import * as THREE from "three";
import {BufferGeometry} from "three/src/core/BufferGeometry";
import {Material} from "three/src/materials/Material";
import {Light} from "three/src/lights/Light";
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import Stats from 'three/examples/jsm/libs/stats.module';
// @ts-ignore
import testPng from '@/assets/test.png'

/**
 * @Author: 曾斌
 * @Date: 2024/2/4 18:15 星期日
 * @Description:
 */
export default class ThreeJs {
    // 有一个场景 scene，场景里面有光线，有雾，有模型Mesh(Mesh模型 - 由几何对象Geometry，材质material组成)...
    // 通过 scene 的 add 方法将 Mesh模型 添加到场景中
    // 然后通过 渲染器WebGLRenderer 使用 相机camera 把这个场景scene 拍下来，展示到页面上

    // 教程: http://webgl3d.cn/pages/b9504a/

    // 加载HDR环境贴图: https://blog.csdn.net/jieyucx/article/details/131392850

    // 画布
    canvas: HTMLElement | null | any = null;
    // 虚拟的3D场景，用来表示模拟生活中的真实三维场景,或者说三维世界
    scene: THREE.Scene | null = null;
    // 几何体API，用来表示三维物体的几何形状
    geometry: THREE.BufferGeometry | null = null;
    // 材质对象Material - 物体的外观效果，比如颜色，就需要通过材质Material相关的API实现
    // MeshBasicMaterial 基础网格材质 不会受到光照影响
    // MeshLambertMaterial 兰伯特网格材质 - 对光照进行漫反射
    // MeshPhongMaterial 高光网格材质 - 材质会呈现出高光
    // MeshStandardMaterial MeshPhysicalMaterial 都是物理材质 - 模拟实际的物理情况进行光照计算，更加逼真
    // MeshPhysicalMaterial是MeshStandardMaterial的扩展，MeshPhysicalMaterial可以更好地控制反射率
    material: THREE.Material | null = null;
    // 网格模型对象Mesh - threejs中可以通过网格模型Mesh表示一个虚拟的物体，比如一个箱子、一个鼠标
    mesh: THREE.Mesh | null = null;
    // 如果想把三维场景Scene渲染到web网页上，还需要一个虚拟相机Camera
    // 正投影相机OrthographicCamera 和 透视投影相机PerspectiveCamera
    // 近裁截面 near 和 远裁截面 far，要能包含你想渲染的场景，否则超出视锥体模型会被剪裁掉，简单说near足够小，far足够大，主要是far。
    camera: THREE.PerspectiveCamera | THREE.OrthographicCamera | null = null;
    // 辅助观察的坐标系
    axesHelper: THREE.AxesHelper | null = null;
    // 渲染器对象 - 相当于执行拍照的人
    renderer: THREE.WebGLRenderer | null = null;
    // 环境光 - AmbientLight - 整个环境的氛围灯
    // 点光源 - PointLight - 像一个灯泡以灯泡为中心向四周发射光线
    // 聚光灯 - SpotLight - 聚光灯一样，从一个点向一个方向发射光线
    // 平行光 - DirectionalLight - 光线像是平行的线一样照亮整个区域
    light: THREE.Light | null = null;
    // 缩放操作
    orbitControl: OrbitControls | null = null;

    constructor(canvas: HTMLElement | null | any) {
        this.canvas = canvas;
        this.init();
    }

    init(): void {
        // 新建一个场景
        this.scene = new THREE.Scene();
        // 相机
        this.setCamera();
        // 模型(物体对象及材质)
        this.setCube();
        // 渲染器
        this.setRenderer();
        // 环境光
        this.setLight();
        // 缩放
        this.setOrbitControl();
        // 动画
        this.animate();
        // 自适应大小
        window.onresize = this.onWindowResize;
        // 渲染帧率，性能统计
        this.initStats();
    }

    /**
     * 新建透视相机
     */
    setCamera(): void {
        // 第二参数就是 长度和宽度比 默认采用浏览器  返回以像素为单位的窗口的内部宽度和高度
        this.camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
        );
        this.camera.position.z = 5;
    }

    /**
     * 设置渲染器
     */
    setRenderer(): void {
        this.renderer = new THREE.WebGLRenderer();
        // 设置画布的大小
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        //这里 其实就是canvas 画布  renderer.domElement
        this.canvas.appendChild(this.renderer.domElement);
    }

    /**
     * 设置环境光
     */
    setLight(): void {
        if (this.scene) {
            // 点光源 - PointLight
            // this.light = new THREE.PointLight(0xffffff, 1);
            // this.light.position.set(-100, -20, 10);
            // this.light.position.set(400, 200, 300);
            // 光源辅助观察
            // const pointLightHelper = new THREE.PointLightHelper(this.light as THREE.PointLight, 20, 0xff0000);
            // pointLightHelper.position.set(400, 200, 300);
            // this.scene.add(pointLightHelper);

            // 环境光 - AmbientLight
            // this.light = new THREE.AmbientLight(0xffffff, 1);
            // 聚光灯 - SpotLight
            // this.light = new THREE.SpotLight(0xffffff, 3, 300, 90);

            // 平行光 - DirectionalLight
            this.light = new THREE.DirectionalLight(0xffffff);
            // 设置光源的方向：通过光源position属性和目标指向对象的position属性计算
            // this.light.position.set(100, 100, 100);
            this.light.position.set(100, 60, 50);
            const directionalLight = this.light as THREE.DirectionalLight;
            // 方向光指向对象网格模型mesh，可以不设置，默认的位置是0,0,0
            directionalLight.target = this.mesh;
            // DirectionalLightHelper：可视化平行光
            const dirLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5, 0xff0000);
            this.scene.add(dirLightHelper);

            this.scene.add(this.light);
        }
    }

    /**
     * 创建网格模型
     */
    setCube(): void {
        if (this.scene) {
            const geometry = new THREE.BoxGeometry(); //创建一个立方体几何对象Geometry
            // const material = new THREE.MeshBasicMaterial({ color: 0xff3200 }); //材质对象Material
            const texture = new THREE.TextureLoader().load(
                testPng
            ); //首先，获取到纹理
            // MeshBasicMaterial 基础网格材质 不会受到光照影响
            // MeshLambertMaterial 兰伯特网格材质 漫反射
            const material = new THREE.MeshLambertMaterial({map: texture});
            this.mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
            this.scene.add(this.mesh); //网格模型添加到场景中
            this.render();
        }
    }

    // 创建stats对象
    stats = Stats();
    /**
     * 渲染帧率，性能统计
     */
    initStats() {
        // stats.domElement显示：渲染帧率  刷新频率,一秒渲染次数
        this.stats.setMode(0);// 默认模式
        // stats.domElement显示：渲染周期 渲染一帧多长时间(单位：毫秒ms)
        // this.stats.setMode(1);
        // stats.domElement:web页面上输出计算结果,一个div元素
        this.canvas.appendChild(this.stats.domElement);
    }

    /**
     * 渲染
     */
    render(): void {
        if (this.renderer && this.scene && this.camera) {
            this.stats.update();
            this.renderer.render(this.scene, this.camera);
        }
    }

    // 渲染循环
    clock = new THREE.Clock();

    /**
     * 动画
     */
    animate(): void {
        if (this.mesh) {
            // 毫秒
            // const spt = this.clock.getDelta() * 1000;
            // console.log('两帧渲染时间间隔(毫秒): ', spt);
            // console.log('帧率FPS: ', 1000 / spt);

            // requestAnimationFrame实现周期性循环执行
            // requestAnimationFrame默认每秒钟执行60次，但不一定能做到，要看代码的性能
            // 对于部分高刷新率的电脑硬件设备，requestAnimationFrame每秒钟默认调用函数执行次数也是有可能超过60次的，
            // 比如你的电脑显卡、显示器等硬件能够支持144hz刷新频率，requestAnimationFrame的每秒执行上限，也可以接近144帧率。
            requestAnimationFrame(this.animate.bind(this));

            this.mesh.rotation.x += 0.01;
            this.mesh.rotation.y += 0.01;
            this.render();
        }
    }

    /**
     * 缩放
     */
    setOrbitControl(): void {
        this.orbitControl = new OrbitControls(this.camera, this.renderer.domElement);
        this.orbitControl.maxDistance = 10;
        this.orbitControl.addEventListener('change', this.animate);
    }

    /**
     * 自适应
     */
    onWindowResize() {
        if (this.camera as THREE.PerspectiveCamera) {
            const c = this.camera as THREE.PerspectiveCamera;
            c.aspect = window.innerWidth / window.innerHeight;
        }
        this.camera.updateProjectionMatrix();
        this.renderer.setSize(window.innerWidth, window.innerHeight);
    }
}
